#include "../LockFreeList.hpp"
#include "../CoarseList.hpp"
#include "../FineList.hpp"
#include "../OptimisticList.hpp"
#include "../OptimisticGlobalList.hpp"
#include "../LazyList.hpp"
#include "iostream"

using namespace std;

//////////////////////////////////////////////////////////////////////////
//Internal Types
//////////////////////////////////////////////////////////////////////////
enum eOpType {
	eOpType_Add,
	eOpType_Rem,
	eOpType_Contain,
	eOpType_None
};

class Configuration {
public:
	bool read (int argc, char **argv) {
		try { 
			if (argc!= 10)
				return false;
			m_testNo = atoi(argv[1]);
			m_algName = argv[2];
			m_numOfThreads = atoi(argv[3]);
			m_numOfOperations = atoi(argv[4]);
			m_maxKey = atoi(argv[5]); 
			m_insertOpsPercent = atoi(argv[6]);
			m_deleteOpsPercent = atoi(argv[7]);
			m_initialCount = atoi(argv[8]);
			m_timeToRun = atoi(argv[9]);

			if (m_algName != "LockFree" && m_algName != "Lazy" && m_algName != "Coarse" &&
				m_algName != "Fine" && m_algName != "Optimistic" && m_algName != "OptimisticGlobal") 
			{
				printf("Illegal algorithmName. Should be one of the following:\n"
					"Coarse\n"
					"Fine\n"
					"Lazy\n"
					"Optimistic\n"
					"OptimisticGlobal\n"
					"LockFree\n\n\n");
				return false;
			}

			return true;
		} catch (std::exception&) {
			return false;
		}
	}

public:
	std::string	m_algName;
	int			m_testNo;
	int			m_numOfThreads;
	int			m_numOfOperations;
	int			m_maxKey;
	int			m_insertOpsPercent;
	int			m_deleteOpsPercent;
	int			m_initialCount;
	int			m_timeToRun;
};

class BenchmarkThread;

//////////////////////////////////////////////////////////////////////////
//Globals
//////////////////////////////////////////////////////////////////////////
set<int>*				g_Set;
tick_t					g_StartTime;
tick_t					g_EndTime;
CMDR::AtomicInteger	g_ThreadCounter(0);
CMDR::AtomicInteger	g_EndCounter(0);
CMDR::AtomicInteger	g_EndWorking(0);

int*						g_RadnNumArray;
eOpType*					g_RandOpArray;

int volatile*			g_acctualAdds;
int volatile*			g_actualDeletes;
int volatile*			g_actualContains;
CMDR::AtomicInteger	g_threadCounter;

Configuration			g_config;
CMDR::Random			g_Rand; 

int						g_numAddThreads;
int						g_numRemThreads;
int						g_numContainThreads;

BenchmarkThread**		g_threads;

//////////////////////////////////////////////////////////////////////////
//Threads
//////////////////////////////////////////////////////////////////////////
class BenchmarkThread: public CMDR::Thread {
public:
	int m_threadNo;
	int m_startValue;
	int m_endValue;
	int m_totalNumOfThreads;


	BenchmarkThread (Configuration config) 
		: Thread()
	{
		m_totalNumOfThreads	= config.m_numOfThreads;
		m_threadNo				= g_threadCounter.getAndIncrement();
		m_startValue			= 5 * m_threadNo;
	}

	virtual void run() {
		//save start benchmark time 
		const int start_counter = g_ThreadCounter.getAndIncrement();
		if(start_counter == (m_totalNumOfThreads-1)) {		
			g_StartTime = CMDR::System::currentTimeMillis();
		}
		//setPriority(_MAX_PRIORITY);
		while(m_totalNumOfThreads != g_ThreadCounter.get()) {;}

		int i=m_startValue;
		
		while (true) {

			switch (g_RandOpArray[i]){
			case eOpType_Add: 
				g_Set->add(g_RadnNumArray[i], m_threadNo);
				g_acctualAdds[m_threadNo]++;
				break;
			case eOpType_Rem: 
				g_Set->remove(g_RadnNumArray[i], m_threadNo);
				g_actualDeletes[m_threadNo]++;
				break;
			case eOpType_Contain:
				g_Set->contains(g_RadnNumArray[i], m_threadNo);
				g_actualContains[m_threadNo]++;
				break;
			}

			if (g_EndWorking.get() == 1)
				break;
			++i;
			if (i >= (g_config.m_numOfOperations - 1))
				i = 0;
		}

		//save end benchmark time
		const int end_counter = g_EndCounter.getAndIncrement();
		if(end_counter == (m_totalNumOfThreads-1)) {
			g_EndTime = CMDR::System::currentTimeMillis();
		}
	}
};

//////////////////////////////////////////////////////////////////////////
//Forward Declarations
//////////////////////////////////////////////////////////////////////////
void usage(){
	printf("<test_exe> <test type> <test number > <alg name> <num of threads> <num of ops> <max key> "
		"<insert ops percent> <delete ops percent> <initial count> <time to run>\n");
}

void runBenchmark();
void PrepareRandomNumbers();
void FillInitialSet();

//////////////////////////////////////////////////////////////////////////
//Main 
//////////////////////////////////////////////////////////////////////////
int main(int argc, char **argv) {
	if (!g_config.read(argc,argv)) {
		usage();
		exit(-1);		
	}	 
	CMDR::Thread::set_concurency_level(g_config.m_numOfThreads); 
	runBenchmark();

	return 1;
}

//////////////////////////////////////////////////////////////////////////
//Helper Functions
//////////////////////////////////////////////////////////////////////////
void runBenchmark() {
	//print test information
	printf("\tConcurrent Set Benchmark \n");
	printf("\t--------------------------- \n");
	printf("\ttestNo:                %d\n", g_config.m_testNo); 
	printf("\tAlgorithem Name:       %s\n", g_config.m_algName.c_str());
	printf("\tnumOfThreads:          %d\n", g_config.m_numOfThreads);	
	printf("\tnoOfActions:           %d\n", g_config.m_numOfOperations);
	printf("\tmaxKey:                %d\n", g_config.m_maxKey);
	printf("\tinsertOps Percentage:  %d\n", g_config.m_insertOpsPercent);
	printf("\tdeleteOps Percentage:  %d\n", g_config.m_deleteOpsPercent);
	printf("\titialCount:            %d\n", g_config.m_initialCount);
	printf("\ttimeToRun:             %d\n", g_config.m_timeToRun);

	//create appropriate Set
	if (strcmp(g_config.m_algName.c_str(), "LockFree") == 0)
		g_Set = new LockFreeList<int>(g_config.m_numOfThreads); 
	else if (strcmp(g_config.m_algName.c_str(), "Coarse") == 0)
		g_Set = new CoarseList<int>(g_config.m_numOfThreads); 
	else if (strcmp(g_config.m_algName.c_str(), "Fine") == 0)
		g_Set = new FineList<int>(g_config.m_numOfThreads);
	else if (strcmp(g_config.m_algName.c_str(), "Optimistic") == 0)
		g_Set = new OptimisticList<int>(g_config.m_numOfThreads);
	else if (strcmp(g_config.m_algName.c_str(), "OptimisticGlobal") == 0)
		g_Set = new OptimisticGlobalList<int>(g_config.m_numOfThreads); 
	else if (strcmp(g_config.m_algName.c_str(), "Lazy") == 0)
		g_Set = new LazyList<int>(g_config.m_numOfThreads); 
	else throw std::exception();

	g_acctualAdds		= new int[g_config.m_numOfThreads];
	g_actualDeletes	= new int[g_config.m_numOfThreads];
	g_actualContains	= new int[g_config.m_numOfThreads];
	g_numAddThreads	= (int)ceil(g_config.m_numOfThreads * g_config.m_insertOpsPercent / 100.0);
	g_numRemThreads	= (int)ceil(g_config.m_numOfThreads * g_config.m_deleteOpsPercent / 100.0);
	g_numContainThreads = g_config.m_numOfThreads - g_numAddThreads - g_numRemThreads;

	for (int i = 0; i < g_config.m_numOfThreads ; i++) {
		g_acctualAdds[i] = 0;
		g_actualDeletes[i] = 0;
		g_actualContains[i] = 0;
	}

	//prepare the random numbers
	printf("");
	printf("START create random numbers\n"); fflush(stdout);
	PrepareRandomNumbers();
	printf("END   creating random numbers\n"); fflush(stdout);
	printf("\n");

	//fill the Fine List
	printf("START fill set (%d)\n", g_config.m_initialCount); fflush(stdout);
	FillInitialSet();
	printf("END   fill set\n");fflush(stdout);
	printf("\n");

	//create benchmark threads
	g_threads = new BenchmarkThread*[g_config.m_numOfThreads];
	printf("START creating threads\n"); fflush(stdout);

	for(int iThread = 0; iThread < g_config.m_numOfThreads; ++iThread) {
		g_threads[iThread ] = new BenchmarkThread(g_config);
	}
  	
	printf("END   creating threads\n"); printf(""); fflush(stdout);

	//start the benchmark threads
	printf("START threads\n");fflush(stdout);

	for(int iThread = 0; iThread < g_config.m_numOfThreads; ++iThread) {
		g_threads[iThread]->start();
	}

	printf("Signal threads to start working\n");fflush(stdout);
	printf("sleeping for %d seconds\n", g_config.m_timeToRun);fflush(stdout);
	CMDR::Thread::sleep(g_config.m_timeToRun*1000);
	g_EndWorking.set(1);

	//signal the threads to start benchmark
	printf("WAITING for threads to terminate\n");fflush(stdout);

	for(int iThread = 0; iThread < g_config.m_numOfThreads; ++iThread) {
		g_threads[iThread]->join();
	}

	printf("ALL threads terminated\n\n\n");fflush(stdout);
	printf("\tTime of benchmark: %u\n",  g_EndTime - g_StartTime);
	printf("\n\tSize of set after test: %d\n\n", g_Set->size(0));
	int totalAdds = 0, totalRemoves = 0, totalContains = 0;

	for (int iThread = 0 ; iThread< g_config.m_numOfThreads; iThread++) {
		printf("\tThread no. %d, Adds: %d ; Deletes: %d ; Contains: %d\n", iThread , g_acctualAdds[iThread], g_actualDeletes[iThread], g_actualContains[iThread]);

		totalAdds += g_acctualAdds[iThread];
		totalRemoves += g_actualDeletes[iThread];
		totalContains += g_actualContains[iThread];
	}

	int time = 10000;(int)(g_EndTime - g_StartTime);

	printf("\n\tTotal Adds: %d ; Time: %d ; Throughput: %f\n", totalAdds, time,(time == 0 ? 0 : (double)totalAdds/time));
	printf("\tTotal Removes: %d ; Time: %d ; Throughput: %f\n", totalRemoves, time,(time == 0 ? 0 : (double)totalRemoves/time));
	printf("\tTotal Contains: %d ; Time: %d ; Throughput: %f\n",totalContains,time, (time == 0 ? 0 : (double)totalContains/time));

	int totalActions = totalAdds + totalRemoves + totalContains;
	printf("\tTotal actions: %d ; Time: %d ; Throughput: %f\n",totalActions,time, (time == 0 ? 0 : (double)totalActions/time));
	

	//delete all threads
	for(int iThread = 0; iThread < g_config.m_numOfThreads; ++iThread) {
		delete g_threads[iThread];
	}
	delete [] g_threads;

	delete g_Set;
	delete [] g_RadnNumArray;
	delete [] g_RandOpArray;

	delete [] g_acctualAdds;
	delete [] g_actualDeletes;
	delete [] g_actualContains;
} 

void PrepareRandomNumbers() {
	int size = g_config.m_numOfOperations;
	g_RadnNumArray = new int[size];
	g_RandOpArray = new eOpType[size];

	int op;
	const int addPercent = g_config.m_insertOpsPercent;
	const int deletePercent = addPercent + g_config.m_deleteOpsPercent;

	if (g_Set != NULL) { 
		for (int iRandNum = 0 ; iRandNum < size ; iRandNum++) {
			g_RadnNumArray[iRandNum] = g_Rand.nextInt(1, g_config.m_maxKey);
			op = g_Rand.nextInt(1,100);
			if (op <= addPercent)
				g_RandOpArray[iRandNum] = eOpType_Add;
			else if (op <= deletePercent)
				g_RandOpArray[iRandNum] = eOpType_Rem;
			else
				g_RandOpArray[iRandNum] = eOpType_Contain;			
		}
	}
}

void FillInitialSet() {
	if (g_Set != NULL) {
		for (int iRandNum = 0 ; iRandNum < g_config.m_initialCount ; iRandNum++){
			g_Set->add(g_Rand.nextInt(1, g_config.m_maxKey), 0);
		} 
		printf ("	Num of elements in Set before start of test: %d\n", g_Set->size(0));
	}
}
